package mongodb.spring;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.connection.SocketSettings;
import mongodb.spring.document.YxpTestDbDoc;
import mongodb.spring.battle.BattleInfo;
import mongodb.spring.battle.Runoob;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.conn.util.DomainType;
import org.bson.BsonDocument;
import org.bson.BsonInt64;
import org.bson.Document;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.conversions.Bson;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.convert.*;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.index.IndexResolver;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static org.springframework.data.mongodb.core.query.Query.query;
import static org.springframework.data.mongodb.core.query.Update.update;

/**
 * @author      : yang.xp
 * @date        : 2021/11/4 18:46
 * @description : spring 关于 mongo的学习 https://docs.spring.io/spring-data/mongodb/docs/3.2.6/reference/html
 * spring 注解的应用 https://www.cnblogs.com/liuchuanfeng/p/7772421.html
 */
public class SpringMongoApp {

    private static final Log log = LogFactory.getLog(SpringMongoApp.class);
    private static MongoDatabase mongoDatabase;
    private static MongoTemplate mongoTemplate;

    public static String getMongodbUrl() {
        String[] split = MongoConfig.hosts.get(0).split(":");
        return "mongodb://" + MongoConfig.username + ":" + MongoConfig.password + "@" + split[0] + ":" + split[1] +
                "/?maxPoolSize=20&w=majority&authSource=" + MongoConfig.database + "&authMechanism=SCRAM-SHA-1";
    }

    public static void main(String[] args) throws Exception {
        System.out.println(getMongodbUrl());
        try (MongoClient mongoClient = MongoClients.create(getMongodbUrl())) {
            MongoDatabase database = mongoClient.getDatabase(MongoConfig.database);
            try {
                Bson command = new BsonDocument("ping", new BsonInt64(1));
                Document commandResult = database.runCommand(command);
                System.out.println("Connected successfully to server.");
            } catch (MongoException me) {
                System.err.println("An error occurred while attempting to run a command: " + me);
            }
        }
    }

    @Before
    public void initMongodb() {
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
                CodecRegistries.fromProviders(pojoCodecProvider));
        MongoClient mongoClient = MongoClients.create(getMongodbUrl());
        this.mongoDatabase = mongoClient.getDatabase(MongoConfig.database).withCodecRegistry(pojoCodecRegistry);
    }

    @Test
    public void insert() {
        Document doc1 = new Document("color", "red").append("qty", 5);
        mongoDatabase.getCollection("person").insertOne(doc1);
    }

    @Test
    public void insertMany() {
        List<Document> list = new ArrayList<>();
        Document doc1 = new Document("color", "green").append("qty", 81);
        Document doc2 = new Document("color", "purple").append("qty", 14);
        list.add(doc1);
        list.add(doc2);
        mongoDatabase.getCollection("person").insertMany(list);
    }

    @Test
    public void queryA() {
        MongoCollection<Document> person = mongoDatabase.getCollection("person");
        Bson filter = Filters.and(Filters.gt("qty", 3), Filters.lt("qty", 9));
        person.find(filter).forEach((Consumer<Document>) document -> {
            System.out.println(document.toJson());
        });
    }

    @Test
    public void aggregateFind() {
        Bson filter = Filters.gt("qty", 4);
        MongoCollection<Document> collection = mongoDatabase.getCollection("person");
        collection.aggregate(Arrays.asList(
                Aggregates.match(filter),
                Aggregates.group("$color", Accumulators.sum("qty", "$qty")),
                Aggregates.sort(Sorts.descending("qty"))))
                .forEach((Consumer<Document>) document -> {
                    System.out.println(document.toJson());
                });
    }

    @Before
    public void initMongoTemplate() {
//        MongoClientOptions.Builder optsBuilder = MongoClientOptions.builder();
//        optsBuilder.connectionsPerHost(MongoConfig.connections);
//        optsBuilder.minConnectionsPerHost(MongoConfig.minConnections);
//        optsBuilder.threadsAllowedToBlockForConnectionMultiplier(MongoConfig.blockMultiplier);
//        optsBuilder.maxWaitTime(MongoConfig.maxWaitTime);

        MongoClientSettings.Builder builder = MongoClientSettings.builder().applyConnectionString(new ConnectionString(getMongodbUrl()));
        builder.applyToSocketSettings(e -> {
            SocketSettings.Builder socketSettingsBuilder = SocketSettings.builder();
            socketSettingsBuilder.connectTimeout(MongoConfig.connectTimeout, TimeUnit.MICROSECONDS);
            socketSettingsBuilder.readTimeout(MongoConfig.socketTimeout, TimeUnit.MICROSECONDS);
        });

        MongoClient mongoClient = MongoClients.create(builder.build());
        SimpleMongoClientDbFactory factory = new SimpleMongoClientDbFactory(mongoClient, MongoConfig.database);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
        MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
        MongoMappingContext mappingContext = new MongoMappingContext();
        mappingContext.setAutoIndexCreation(true);
        mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
        mappingContext.afterPropertiesSet();

        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
        converter.setCustomConversions(conversions);
        converter.setCodecRegistryProvider(factory);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 去除_class字段
        converter.afterPropertiesSet();
        this.mongoTemplate = new MongoTemplate(factory, converter);
    }

    @Test
    public void queryQ() {
        Query query = new Query();
        query.addCriteria(Criteria.where("color").is("red"));
        mongoTemplate.find(query, Color.class).forEach((Consumer) doc -> {
            System.out.println(doc.toString());
        });
        // Query 查询器  Update 更新器
        // Criteria 条件选择器
        // mongoTemplate 查询模板
        // Document 文档
        // Aggregation 聚合器 ProjectionOperation 聚合操作 AggregationResults 聚合操作结果
    }

    @Test
    public void insertQ(){
        Person p = new Person("Joe", 34);

        // Insert is used to initially store the object into the database.
        mongoTemplate.insert(p);
        log.info("Insert: " + p);

        // Find
        p = mongoTemplate.findById(p.getId(), Person.class);
        log.info("Found: " + p);

        // Update
        mongoTemplate.updateFirst(query(Criteria.where("name").is("Joe")), update("age", 35), Person.class);
        p = mongoTemplate.findOne(query(Criteria.where("name").is("Joe")), Person.class);
        log.info("Updated: " + p);

        // Delete
        mongoTemplate.remove(p);

        // Check that deletion worked
        List<Person> people =  mongoTemplate.findAll(Person.class);
        log.info("Number of people = : " + people.size());

        mongoTemplate.dropCollection(Person.class);
    }

    @Test
    public void selectVideoInfo() {

        Query query = new Query();
        query.addCriteria(Criteria.where("playerId").gt(0));
        query.addCriteria(new Criteria().orOperator(Criteria.where("likeCount").gte(10),
                Criteria.where("first").is(true)));
        query.with(Sort.by(Sort.Direction.DESC, "id")).limit(100);
        query.withHint(new Document("_id", 1));
        long start = System.currentTimeMillis();
        List<VideoInfo> videoInfos = mongoTemplate.find(query, VideoInfo.class);
        long end = System.currentTimeMillis();
        System.out.println("CostTime : = " + (end - start));
    }

    @Test
    public void queryYxpTestDb(){
        Query query1 = new Query();
        query1.addCriteria(Criteria.where("playerId").gt(0));
        List<YxpTestDbDoc> yxpTestDbDocs = mongoTemplate.find(query1, YxpTestDbDoc.class);
        yxpTestDbDocs.forEach(System.out::println);
    }

    @Test
    public void addFields(){
        Update update = new Update();
        update.set("addFi", 1);
        Query query1 = new Query();
        query1.addCriteria(Criteria.where("playerId").gt(1));
        YxpTestDbDoc andModify = mongoTemplate.findAndModify(query1, update, YxpTestDbDoc.class);
    }


    // https://blog.csdn.net/weixin_44285988/article/details/99448931
//    db.runoob2.insert({
//        _id: 1003,
//                likemsg: [{
//            _id: 1002,
//                    "battleId": 33
//        }, {
//            _id: 1003,
//                    "battleId": 33
//        }]
//    })
    @Test
    public void updateFirstToSet() {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(1002));
        // elemMatch 条件必须要按照顺序精确匹配，反向就不行
        int id = 1003;
        int battleId = 37;
//        query.addCriteria(Criteria.where("likemsg").elemMatch(Criteria.where("id").is(id).and("battleId").is(battleId)));

        List<Runoob> runoobs = mongoTemplate.find(query, Runoob.class);
        for (Runoob runoob : runoobs) {
            System.out.println(runoob);
        }

        UpdateResult likemsg = null;
        try {
            likemsg = mongoTemplate.updateFirst(query, new Update().addToSet("likemsg", new BattleInfo(id, battleId)), Runoob.class);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        if (likemsg.getModifiedCount() > 0) {
            System.out.println("hello world");
        }
    }

}
